Skip to content

S01-27 JavaSE-正则

[TOC]

为什么要学习正则表达式

极速体验正则表达式威力

Regexp_.java

  1. 提取文章中所有的英文单词
  2. 提取文章中所有的数字
  3. 提取文章中所有的英文单词和数字
  4. 提取百度热榜标题
java
//示例:提取英文单词
Pattern pattern = Pattern.compile("[a-zA-Z]+");
//提取数字
//Pattern pattern = Pattern.compile("[0-9]+");
//提取英文单词和数字
//Pattern pattern = Pattern.compile("([0-9]+)|([a-zA-Z]+)");
//提取百度热榜标题
//Pattern pattern = Pattern.compile("<a target=\"_blank\" title=\"(\\S*)\"");

结论:正则表达式是处理文本的利器。

常见问题

  1. 找出所有四个数字连在一起的子串?
  2. 找出所有四个数字连在一起的子串,且第一位与第四位相同、第二位与第三位相同(如1221、5775)?
  3. 验证输入的邮件是否符合电子邮件格式?
  4. 验证输入的手机号是否符合手机号格式?

解决之道-正则表达式

  1. Java提供了正则表达式技术,专门用于处理文本匹配问题。
  2. 正则表达式:Regular Expression(RegExp),是对字符串执行模式匹配的技术。
  3. 跨语言特性:不仅Java支持,Python、JavaScript、C#等多数编程语言都支持。

正则表达式基本介绍

  1. 一个正则表达式是用某种模式去匹配字符串的公式,看似复杂但灵活高效,能将数小时的文本处理工作缩短到几分钟。
  2. 核心作用:字符串匹配(查找、替换、验证、分割)。

正则表达式底层实现

实例分析

RegTheory.java:分析正则表达式匹配的底层原理

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 分析java 的正则表达式的底层实现(重要)
 */
public class RegTheory {
    public static void main(String[] args) {
        String content = "1998 年12 月8 日,第二代Java 平台的企业版J2EE 发布。1999 年6 月,Sun 公司发布了" +
                "第二代Java 平台(简称为Java2)的3 个版本:J2ME(Java2 Micro Edition,Java2 平台的微型" +
                "版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition,Java 2 平台的" +
                "标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2 平台的企业版),应" +
                "用3443 于基于Java 的应用服务器。Java 2 平台的发布,是Java 发展过程中最重要的一个" +
                "里程碑,标志着Java 的应用开始普及9889 ";

        //目标:匹配所有四个数字
        String regStr = "(\\d\\d)(\\d\\d)"; //分组:(第一组)(第二组)

        //1. 创建模式对象(正则表达式对象)
        Pattern pattern = Pattern.compile(regStr);

        //2. 创建匹配器:按照正则表达式规则匹配content字符串
        Matcher matcher = pattern.matcher(content);

        //3. 开始匹配
        /**
         * matcher.find() 完成的任务(考虑分组):
         * 1. 根据指定规则定位满足规则的子字符串(如(19)(98))
         * 2. 找到后,将子字符串的开始索引记录到matcher的groups数组:
         *    groups[0] = 开始索引,groups[1] = 结束索引+1(整个匹配串)
         *    groups[2] = 第一组开始索引,groups[3] = 第一组结束索引+1
         *    groups[4] = 第二组开始索引,groups[5] = 第二组结束索引+1
         * 3. 记录oldLast为子字符串的结束索引+1,下次find从该位置开始
         *
         * matcher.group(int group):
         * 根据groups数组的索引,截取子字符串返回
         * group(0):整个匹配串(groups[0]~groups[1])
         * group(1):第一组匹配串(groups[2]~groups[3])
         * group(2):第二组匹配串(groups[4]~groups[5])
         */
        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
            System.out.println("第1 组()匹配到的值 =" + matcher.group(1));
            System.out.println("第2 组()匹配到的值=" + matcher.group(2));
        }
    }
}

正则表达式语法

正则表达式的元字符从功能上分为6类:字符匹配符、选择匹配符、限定符、分组组合和反向引用符、特殊字符、定位符。

元字符-转义号\\

在匹配特殊字符(如.*+()$/\?[]^{})时,需要使用转义符\\,否则会被当作正则元字符解析。

注意:在Java中,\\代表其他语言中的一个\

示例:

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示转义字符的使用
 */
public class RegExp02 {
    public static void main(String[] args) {
        String content = "abc$(a.bc(123( )";

        //匹配( => \\(
        //String regStr = "\\(";
        //匹配. => \\.
        //String regStr = "\\.";
        //匹配3个数字
        String regStr = "\\d{3}";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

元字符-字符匹配符

符号含义示例说明匹配输入
[]可接收的字符列表[efgh]e、f、g、h中的任意1个字符e、f、g、h
[^]不接收的字符列表[^abc]除a、b、c之外的任意1个字符(含数字和特殊符号)d、1、#
-连字符A-Z任意单个大写字母A、B、Z
.匹配除\n以外的任何字符a..b以a开头、b结尾,中间2个任意字符(长度为4)aaab、a35b、a#*b
\\d匹配单个数字字符(等价于[0-9]\\d{3}(\\d)?3个或4个数字123、9876
\\D匹配单个非数字字符(等价于[^0-9]\\D(\\d)*非数字开头,后接任意数字a、A342
\\w匹配数字、大小写字母、下划线(等价于[0-9a-zA-Z_]\\d{3}\\w{4}3个数字开头,共7位数字字母下划线234abcd、12345Pe
\\W匹配非数字、大小写字母、下划线(等价于[^0-9a-zA-Z_]\\W+\\d{2}至少1个非数字字母开头,2个数字结尾#29、#?@10
\\s匹配任意空白字符(空格、制表符等)a\\sba和b之间有一个空白字符a b、a\tb
\\S匹配任意非空白字符(与\\s相反)a\\Sba和b之间有一个非空白字符aab、a#b

示例:

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示字符匹配符的使用
 */
public class RegExp03 {
    public static void main(String[] args) {
        String content = "a11c8abc _ABCy @";

        //String regStr = "[a-z]";//匹配a-z之间任意一个字符
        //String regStr = "[A-Z]";//匹配A-Z之间任意一个字符
        //String regStr = "abc";//匹配abc字符串(默认区分大小写)
        //String regStr = "(?i)abc";//匹配abc字符串(不区分大小写)
        //String regStr = "[0-9]";//匹配0-9之间任意一个字符
        //String regStr = "[^a-z]";//匹配不在a-z之间任意一个字符
        //String regStr = "[^0-9]";//匹配不在0-9之间任意一个字符
        //String regStr = "[abcd]";//匹配在abcd中任意一个字符
        //String regStr = "\\w";//匹配大小写英文字母、数字、下划线
        //String regStr = "\\W";//匹配等价于[^a-zA-Z0-9_]
        //String regStr = "\\s";//匹配任何空白字符
        //String regStr = "\\S";//匹配任何非空白字符
        String regStr = ".";//匹配除\n之外的所有字符(匹配.本身需用\\.)

        //创建Pattern时指定Pattern.CASE_INSENSITIVE表示不区分大小写
        Pattern pattern = Pattern.compile(regStr/*, Pattern.CASE_INSENSITIVE*/);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

元字符-选择匹配符

符号含义示例说明匹配输入
``匹配“”之前或之后的表达式`ab

示例:

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 选择匹配符
 */
public class RegExp04 {
    public static void main(String[] args) {
        String content = "hanshunping 韩顺平 寒冷";
        String regStr = "han|韩|寒";//匹配han、韩或寒

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

元字符-限定符

用于指定其前面的字符或组合项连续出现的次数。

符号含义示例说明匹配输入
*0次或多次(零到多)(abc)*任意个abc(含0个)""、abc、abcabc
+1次或多次(一到多)m+(abc)*至少1个m开头,后接任意个abcm、mabc、mabcabc
?0次或1次(最多一次)m+abc?至少1个m开头,后接ab或abcmab、mabc、mmab
{n}恰好n次[abcd]{3}由abcd中字母组成的长度为3的字符串abc、dbc、ade
{n,}至少n次[abcd]{3,}由abcd中字母组成的长度≥3的字符串abc、abcd、aaabdc
{n,m}至少n次,最多m次[abcd]{3,5}由abcd中字母组成的长度3~5的字符串abc、abcd、aaaaa

注意:Java匹配默认是贪婪匹配(尽可能匹配多的字符)。

示例:

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示限定符的使用
 */
public class RegExp05 {
    public static void main(String[] args) {
        String content = "a211111aaaaaahello";

        //String regStr = "a{3}";//匹配aaa
        //String regStr = "1{4}";//匹配1111
        //String regStr = "\\d{2}";//匹配两位数字
        //String regStr = "a{3,4}";//匹配aaa或aaaa(贪婪匹配)
        //String regStr = "1{4,5}";//匹配1111或11111
        //String regStr = "\\d{2,5}";//匹配2~5位数字
        //String regStr = "1+";//匹配1个或多个1
        //String regStr = "\\d+";//匹配1个或多个数字
        //String regStr = "1*";//匹配0个或多个1
        String regStr = "a1?";//匹配a或a1

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

元字符-定位符

规定要匹配的字符串出现的位置(起始、结束、边界)。

符号含义示例说明匹配输入
^指定起始位置^[0-9]+[a-z]*以至少1个数字开头,后接任意个小写字母123、6aa、555edf
$指定结束位置^[0-9]+-[a-z]+$以1个数字开头,接连字符“-”,以至少1个小写字母结尾1-a、123-abc
\\b匹配目标字符串的边界han\\b子串间有空格或目标字符串结束的位置hanshunping(末尾han)、sp han(空格后han)
\\B匹配目标字符串的非边界han\\B\\b相反(子串内部)hanshunping(开头han)、nhan(n后han)

示例:

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示定位符的使用
 */
public class RegExp06 {
    public static void main(String[] args) {
        String content = "hanshunping sphan nnhan";
        //String content = "123-abc";

        //以至少1个数字开头,后接任意个小写字母
        //String regStr = "^[0-9]+[a-z]*";
        //以至少1个数字开头,必须以至少1个小写字母结尾
        //String regStr = "^[0-9]+\\-[a-z]+$";
        //匹配边界的han(空格后或字符串末尾)
        //String regStr = "han\\b";
        //匹配非边界的han(子串内部)
        String regStr = "han\\B";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

分组组合和反向引用符

分组构造形式说明
(pattern)非命名捕获:捕获匹配的子字符串,编号从0开始(0为整个表达式,1~n为分组)
(?<name>pattern)命名捕获:将匹配的子字符串捕获到指定名称的组中(name不能含标点,不能以数字开头)
(?:pattern)非捕获匹配:匹配pattern但不存储结果,用于组合模式
(?=pattern)正向预查:匹配pattern前面的位置(不捕获pattern)
(?!pattern)负向预查:匹配不处于pattern前面的位置(不捕获pattern)

示例1:命名分组

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 分组:命名分组和非命名分组
 */
public class RegExp07 {
    public static void main(String[] args) {
        String content = "hanshunping s7789 nn1189han";

        //非命名分组:(\\d\\d)(\\d\\d)
        //命名分组:(?<g1>\\d\\d)(?<g2>\\d\\d)
        String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";//匹配4个数字

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
            System.out.println("第1个分组内容=" + matcher.group(1));
            System.out.println("第1个分组内容[通过组名]=" + matcher.group("g1"));
            System.out.println("第2个分组内容=" + matcher.group(2));
            System.out.println("第2个分组内容[通过组名]=" + matcher.group("g2"));
        }
    }
}

示例2:非捕获分组

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示非捕获分组
 */
public class RegExp08 {
    public static void main(String[] args) {
        String content = "hello 韩顺平教育jack 韩顺平老师韩顺平同学hello 韩顺平学生";

        //需求1:找到韩顺平教育、韩顺平老师、韩顺平同学(非捕获分组)
        //String regStr = "韩顺平(?:教育|老师|同学)";

        //需求2:找到韩顺平教育和韩顺平老师中的"韩顺平"(正向预查)
        //String regStr = "韩顺平(?=教育|老师)";

        //需求3:找到不是韩顺平教育和韩顺平老师中的"韩顺平"(负向预查)
        String regStr = "韩顺平(?!教育|老师)";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            System.out.println("找到: " + matcher.group(0));
        }
    }
}

应用实例

字符串验证(RegExp10.java)

验证以下格式:

  1. 汉字:^[\u0391-\uffe5]+$
  2. 邮政编码:1-9开头的6位数:^[1-9]\\d{5}$
  3. QQ号码:1-9开头的5-10位数:^[1-9]\\d{4,9}$
  4. 手机号码:13、14、15、18开头的11位数:^1[3|4|5|8]\\d{9}$
java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 正则表达式的应用实例:字符串验证
 */
public class RegExp10 {
    public static void main(String[] args) {
        String content = "13588889999";

        //验证汉字
        //String regStr = "^[\\u0391-\\uffe5]+$";

        //验证邮政编码
        //String regStr = "^[1-9]\\d{5}$";
        //验证QQ号码
        //String regStr = "^[1-9]\\d{4,9}$";
        //验证手机号码
        String regStr = "^1[3|4|5|8]\\d{9}$";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        if(matcher.find()) {
            System.out.println("满足格式");
        } else {
            System.out.println("不满足格式");
        }
    }
}

URL验证(RegExp11.java)

URL格式:^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?$

java
package com.hspedu.regexp;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示URL验证
 */
public class RegExp11 {
    public static void main(String[] args) {
        String content = "https://www.bilibili.com/video/BV1fh411y7R8?from=search&seid=1831060912083761326";
        //String content = "http://edu.3dsmax.tech/yg/bilibili/my6652/pc/qg/05-51/index.html#201211-1?track_id=xxx";

        /**
         * 思路分析:
         * 1. 协议部分:(http|https):// (可选)
         * 2. 域名部分:([\\w-]+\\.)+[\\w-]+ (如www.bilibili.com)
         * 3. 路径和参数部分:(\\/[\\w-?=&/%.#]*)? (可选,如/video/BV1fh411y7R8?from=search)
         */
        String regStr = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/%.#]*)?$";

        Pattern pattern = Pattern.compile(regStr);
        Matcher matcher = pattern.matcher(content);

        System.out.println("是否满足URL格式:" + matcher.find());
        //或